#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#include "ysh.h"
#include "la.h"
#include "error.h"
#include "parse.h"
#include "pool.h"

typedef struct ReservedWord {
    TokenType rwt;
    char *rwb;
} ReservedWord;

static ReservedWord reserved_word[] = {
    {TT_IEXIT, "exit"},
    {TT_IJOBS, "jobs"},
    {TT_IECHO, "echo"},
    {TT_IFG, "fg"},
    {TT_IBG, "bg"},
    {TT_HISTORY, "history"},
    {TT_CD, "cd"},
    {TT_EXEC, "exec"},
    {TT_STRING, (char *)NULL},
};

static char META_CHAR[] = {'<', '>', '|', '$', '&', '%', ' ', '\n'};

typedef enum LaState {
    LS_START, LS_STRING, LS_ECHOSTATUS, LS_PERCENTINT, LS_END,
} LaState;

char line_buf[LINE_BUF_SIZE] = {0};
int buf_size = 0;
static int col = 0;
static int last_col = 0;
static Bool in_line_begin = TRUE;

char token_buf[TOKEN_BUF_SIZE] = {0};
int token_buf_size = 0;
TokenType current_token = TT_ERRORTOKEN;

static LaState current_state = LS_START;

static Bool check_blank_string(char *str)
{
    char *p;
    p = str;
    while (*p != '\0') {
        if (isspace(*p) == 0) {
            return FALSE;
        }
        else {
            p++;
        }
    }
    return TRUE;
}

static void check_internal(void)
{
    int i;
    for (i = 0; reserved_word[i].rwb != NULL; i++) {
        if (strcmp(token_buf, reserved_word[i].rwb) == 0) {
            current_token = reserved_word[i].rwt;
        }
    }
}

static char get_char(void)
{
    if (col == buf_size) {
        //buf_size = read(STDIN_FILENO, line_buf, LINE_BUF_SIZE);
        fgets(line_buf, LINE_BUF_SIZE, stdin);
        buf_size = strlen(line_buf);
        if (check_blank_string(line_buf) == FALSE) {
            cat_to_raw(line_buf, buf_size);
            insert_history(line_buf);
        }
        col = 0;
    }
    return line_buf[col++];
}

static void put_back_and_end(void)
{
    current_state = LS_END;
    // Maybe here gets no need to check 'col'.
    if (col > 0) {
        col--;
    }
}

void put_back(void)
{
    col = last_col;
}

static TokenType get_token(void) {
    char current_char = (char)0;
    //char *t_char_p = NULL;

    last_col = col;
    current_state = LS_START;
    token_buf_size = 0;
    do {
        if (current_state != LS_END) {
            current_char = get_char();
            if (current_state == LS_START &&
                isspace(current_char) != 0 &&
                current_char != '\n') {
                continue;
            }
        }

        switch (current_state) {
            case LS_START:
               switch (current_char) {
                    case '<':
                        current_state = LS_END;
                        current_token = TT_INREDIRECT;
                        break;
                    case '>':
                        current_state = LS_END;
                        current_token = TT_OUTREDIRECT;
                        break;
                    case '|':
                        current_state = LS_END;
                        current_token = TT_PIPE;
                        break;
                    case '&':
                        current_state = LS_END;
                        current_token = TT_BACKGROUND;
                        break;
                    case '\n':
                        current_state = LS_END;
                        current_token = TT_EOLTOKEN;
                        break; 
                    default:
                        current_state = LS_STRING;
                        current_token = TT_STRING;
                        token_buf[token_buf_size++] = (current_char == '\\') ? get_char() : current_char;
                        break;
                }
                break;
            case LS_STRING:
                if (current_char == '\\') {
                    token_buf[token_buf_size++] = get_char();
                }
                else if (strchr(META_CHAR, current_char) != NULL) {
                    put_back_and_end();
                }
                else {
                    token_buf[token_buf_size++] = current_char;
                }
                break;
           default:
                break;
        }
        if (current_state == LS_END) {
            token_buf[token_buf_size] = '\0';
            //printf("%d, %d, %s\n", current_token, token_buf_size, token_buf);
            // For the concern of internal command string.
            if (in_line_begin == TRUE && current_token == TT_STRING) {
                check_internal();
                in_line_begin = FALSE;
            }
            if (current_token == TT_EOLTOKEN) {
                in_line_begin = TRUE;
            }
        }
    } while (current_state != LS_END);

    return current_token;
}

void read_string(TokenType *tt, char *tb)
{
    char tc;
    char *ttb = tb;
    Bool is_percent_int = FALSE;

    do {
        tc = get_char();
    } while (isspace(tc) != 0 && tc != '\n');

    if (tc == '%') {
        is_percent_int = TRUE;
    }
    if (tc == '\n') {
        *tt = TT_EOLTOKEN;
        *tb = '\0';
        in_line_begin = TRUE;
//        col--;
        return;
    }
    do {
        if (tc == '\\') {
            tc = get_char();
        }
        *tb = tc;
        tb++;
        tc = get_char();
        if (isdigit(tc) == 0 && isspace(tc) == 0) {
            is_percent_int = FALSE;
        }
    } while (isspace(tc) == 0);
    col--;
    *tb = '\0';

    if (strcmp(ttb, "$status") == 0) {
        *tt = TT_ECHOSTATUS;
    }
    else if (is_percent_int == TRUE) {
        *tt = TT_PERCENTINT;
    }
    else {
        *tt = TT_STRING;
    }
}

TokenType read_token(void)
{
    return get_token();
}

